﻿//
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
//
//  License: GNU General Public License (GPLv2)
//
//  Email: pavel_torgashov@mail.ru.
//
//  Copyright (C) Pavel Torgashov, 2012. 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using FarsiLibrary.Win;
using FastColoredTextBoxNS;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Drawing.Drawing2D;

namespace CLEditor
{
    public partial class CLEditorForm : Form
    {
        string[] snippets = { "if(^)\n{\n;\n}", "if(^)\n{\n;\n}\nelse\n{\n;\n}", "for(^;;)\n{\n;\n}", "while(^)\n{\n;\n}" };
        string[] declarationSnippets = { 
               "__kernel void ^()\n{\n;\n}",
               "void ^()\n{\n;\n}"
               };
        Style invisibleCharsStyle = new InvisibleCharsRenderer(Pens.Gray);
        Color currentLineColor = Color.FromArgb(200, 210, 210, 255);
        Color changedLineColor = Color.FromArgb(255, 230, 230, 255);
        AutocompleteMenu popupMenu;


        public CLEditorForm()
        {
            InitializeComponent();

            //init menu images
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CLEditorForm));
            copyToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("copyToolStripButton.Image")));
            cutToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("cutToolStripButton.Image")));
            pasteToolStripMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("pasteToolStripButton.Image")));
        }


        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CreateTab(null);
        }

        private void CreateTab(string fileName)
        {
            try
            {
                var tb = new FastColoredTextBox();
                tb.ContextMenuStrip = cmMain;
                tb.Dock = DockStyle.Fill;
                tb.BorderStyle = BorderStyle.Fixed3D;
                tb.LeftPadding = 1;
                tb.Language = Language.Custom;
                tb.AddStyle(new MarkerStyle(new SolidBrush(Color.FromArgb(50, Color.Gray))));//same words style
                tb.DelayedTextChangedInterval = 1000;
                tb.DelayedEventsInterval = 1000;
                tb.TextChangedDelayed += new EventHandler<TextChangedEventArgs>(tb_TextChangedDelayed);
                tb.TextChanged += new EventHandler<TextChangedEventArgs>(tb_TextChanged);
                tb.SelectionChangedDelayed += new EventHandler(tb_SelectionChangedDelayed);
                tb.KeyDown += new KeyEventHandler(tb_KeyDown);
                tb.LineRemoved += new EventHandler<LineRemovedEventArgs>(tb_LineRemoved);
                tb.PaintLine += new EventHandler<PaintLineEventArgs>(tb_PaintLine);
                tb.ChangedLineColor = changedLineColor;
                tb.HighlightingRangeType = HighlightingRangeType.VisibleRange;

                tb.Tag = new TbInfo();
                var tab = new FATabStripItem(fileName != null ? Path.GetFileName(fileName) : "[new]", tb);
                if (fileName != null)
                    tb.Text = File.ReadAllText(fileName);
                tab.Tag = fileName;
                tb.ClearUndo();
                tb.IsChanged = false;
                tsFiles.AddTab(tab);
                tsFiles.SelectedItem = tab;
                tb.Focus();

                //create autocomplete popup menu
                popupMenu = new AutocompleteMenu(tb);
                popupMenu.Items.ImageList = ilAutocomplete;
                popupMenu.Opening += new EventHandler<CancelEventArgs>(popupMenu_Opening);
                BuildAutocompleteMenu(popupMenu);
                (tb.Tag as TbInfo).popupMenu = popupMenu;
            }
            catch (Exception ex)
            {
                if (MessageBox.Show(ex.Message, "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == System.Windows.Forms.DialogResult.Retry)
                    CreateTab(fileName);
            }
        }

        void tb_TextChanged(object sender, TextChangedEventArgs e)
        {
            var list = ((sender as FastColoredTextBox).Tag as TbInfo).explorerList;
            CLSyntaxHighlighter.Highlight(e.ChangedRange, list);
        }

        void popupMenu_Opening(object sender, CancelEventArgs e)
        {
            //---block autocomplete menu for comments
            //get index of green style (used for comments)
            var iGreenStyle = CurrentTB.GetStyleIndex(CurrentTB.SyntaxHighlighter.GreenStyle);
            if (iGreenStyle >= 0)
                if (CurrentTB.Selection.Start.iChar > 0)
                {
                    //current char (before caret)
                    var c = CurrentTB[CurrentTB.Selection.Start.iLine][CurrentTB.Selection.Start.iChar - 1];
                    //green Style
                    var greenStyleIndex = Range.ToStyleIndex(iGreenStyle);
                    //if char contains green style then block popup menu
                    if ((c.style & greenStyleIndex) != 0)
                        e.Cancel = true;
                }
        }

        void tb_PaintLine(object sender, PaintLineEventArgs e)
        {
            TbInfo info = (sender as FastColoredTextBox).Tag as TbInfo;

            //draw bookmark
            if (info.bookmarksLineId.Contains((sender as FastColoredTextBox)[e.LineIndex].UniqueId))
            {
                e.Graphics.FillEllipse(new LinearGradientBrush(new Rectangle(0, e.LineRect.Top, 15, 15), Color.White, Color.PowderBlue, 45), 0, e.LineRect.Top, 15, 15);
                e.Graphics.DrawEllipse(Pens.PowderBlue, 0, e.LineRect.Top, 15, 15);
            }
        }

        void tb_LineRemoved(object sender, LineRemovedEventArgs e)
        {
            TbInfo info = (sender as FastColoredTextBox).Tag as TbInfo;

            //remove lines from bookmarks
            foreach (int id in e.RemovedLineUniqueIds)
                if (info.bookmarksLineId.Contains(id))
                {
                    info.bookmarksLineId.Remove(id);
                    info.bookmarks.Remove(id);
                }
        }



        private void BuildAutocompleteMenu(AutocompleteMenu popupMenu)
        {
            List<AutocompleteItem> items = new List<AutocompleteItem>();

            if(CurrentTB != null)
            foreach (var item in (CurrentTB.Tag as TbInfo).explorerList)
                items.Add(new AutocompleteItem(item.funcName) { ImageIndex = 2});

            foreach (var item in snippets)
                items.Add(new SnippetAutocompleteItem(item) { ImageIndex = 1 });
            foreach (var item in CLSyntaxHighlighter.GetAllKeywords())
                items.Add(new AutocompleteItem(item));
            foreach (var item in declarationSnippets)
                items.Add(new DeclarationSnippet(item) { ImageIndex = 0 });

            items.Add(new InsertSpaceSnippet(@"^(\d+)([a-zA-Z_]+)(\d*)$"));
            items.Add(new InsertSpaceSnippet(@"^(\w+)([=<>!:]+)(\w+)$"));
            items.Add(new InsertEnterSnippet());

            //set as autocomplete source
            popupMenu.Items.SetAutocompleteItems(items);
            popupMenu.SearchPattern = @"[\w\.:=!<>]";
        }

        void tb_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Modifiers == Keys.Control && e.KeyCode == Keys.OemMinus)
            {
                NavigateBackward();
                e.Handled = true;
            }

            if (e.Modifiers == (Keys.Control|Keys.Shift) && e.KeyCode == Keys.OemMinus)
            {
                NavigateForward();
                e.Handled = true;
            }

            if (e.KeyData == (Keys.K | Keys.Control))
            {
                //forced show (MinFragmentLength will be ignored)
                (CurrentTB.Tag as TbInfo).popupMenu.Show(true);
                e.Handled = true;
            }
        }

        void tb_SelectionChangedDelayed(object sender, EventArgs e)
        {
            var tb = sender as FastColoredTextBox;
            //remember last visit time
            if (tb.Selection.Start == tb.Selection.End && tb.Selection.Start.iLine < tb.LinesCount)
            {
                if (lastNavigatedDateTime != tb[tb.Selection.Start.iLine].LastVisit)
                {
                    tb[tb.Selection.Start.iLine].LastVisit = DateTime.Now;
                    lastNavigatedDateTime = tb[tb.Selection.Start.iLine].LastVisit;
                }
            }

            //highlight same words
            tb.Range.ClearStyle(tb.Styles[0]);
            if (tb.Selection.Start != tb.Selection.End)
                return;//user selected diapason
            //get fragment around caret
            var fragment = tb.Selection.GetFragment(@"\w");
            string text = fragment.Text;
            if (text.Length == 0)
                return;
            //highlight same words
            var ranges = tb.Range.GetRanges("\\b" + text + "\\b");
            foreach (var r in ranges)
                r.SetStyle(tb.Styles[0]);
        }

        void tb_TextChangedDelayed(object sender, TextChangedEventArgs e)
        {
            //rebuild object explorer
            ThreadPool.QueueUserWorkItem(
                (o)=>BuildObjectExplorer()
            );
        }

        private void BuildObjectExplorer()
        {
            if (CurrentTB != null)
                try
                {
                    var list = CLSyntaxHighlighter.BuildExplorerItems(CurrentTB.Range);
                    (CurrentTB.Tag as TbInfo).explorerList = list;
                    CLSyntaxHighlighter.Highlight(CurrentTB.Range, list);
                    BuildAutocompleteMenu(popupMenu);
                }
                catch { ;}
        }

        private void tsFiles_TabStripItemClosing(TabStripItemClosingEventArgs e)
        {
            if ((e.Item.Controls[0] as FastColoredTextBox).IsChanged)
            {
                switch(MessageBox.Show("Do you want save " + e.Item.Title + " ?", "Save", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information))
                {
                    case System.Windows.Forms.DialogResult.Yes:
                        if (!Save(e.Item))
                            e.Cancel = true;
                        break;
                    case DialogResult.Cancel:
                         e.Cancel = true;
                        break;
                }
            }
        }

        private bool Save(FATabStripItem tab)
        {
            var tb = (tab.Controls[0] as FastColoredTextBox);
            if (tab.Tag == null)
            {
                if (sfdMain.ShowDialog() != System.Windows.Forms.DialogResult.OK)
                    return false;
                tab.Title = Path.GetFileName(sfdMain.FileName);
                tab.Tag = sfdMain.FileName;
            }

            try
            {
                File.WriteAllText(tab.Tag as string, tb.Text);
                tb.IsChanged = false;
            }
            catch (Exception ex)
            {
                if (MessageBox.Show(ex.Message, "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
                    return Save(tab);
                else
                    return false;
            }

            tb.Invalidate();

            return true;
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (tsFiles.SelectedItem != null)
                Save(tsFiles.SelectedItem);
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (tsFiles.SelectedItem != null)
            {
                string oldFile = tsFiles.SelectedItem.Tag as string;
                tsFiles.SelectedItem.Tag = null;
                if (!Save(tsFiles.SelectedItem))
                if(oldFile!=null)
                {
                    tsFiles.SelectedItem.Tag = oldFile;
                    tsFiles.SelectedItem.Title = Path.GetFileName(oldFile);
                }
            }
        }

        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (ofdMain.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                CreateTab(ofdMain.FileName);
        }

        FastColoredTextBox CurrentTB
        {
            get {
                if (tsFiles.SelectedItem == null)
                    return null;
                return (tsFiles.SelectedItem.Controls[0] as FastColoredTextBox);
            }

            set
            {
                tsFiles.SelectedItem = (value.Parent as FATabStripItem);
                value.Focus();
            }
        }

        private void cutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.Cut();
        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.Copy();
        }

        private void pasteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.Paste();
        }

        private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.Selection.SelectAll();
        }

        private void undoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (CurrentTB.UndoEnabled)
                CurrentTB.Undo();
        }

        private void redoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (CurrentTB.RedoEnabled)
                CurrentTB.Redo();
        }

        private void tmUpdateInterface_Tick(object sender, EventArgs e)
        {
            try
            {
                if(CurrentTB != null && tsFiles.Items.Count>0)
                {
                    var tb = CurrentTB;
                    undoStripButton.Enabled = undoToolStripMenuItem.Enabled = tb.UndoEnabled;
                    redoStripButton.Enabled = redoToolStripMenuItem.Enabled = tb.RedoEnabled;
                    saveToolStripButton.Enabled = saveToolStripMenuItem.Enabled = tb.IsChanged;
                    saveAsToolStripMenuItem.Enabled = true;
                    pasteToolStripButton.Enabled = pasteToolStripMenuItem.Enabled = true;
                    cutToolStripButton.Enabled = cutToolStripMenuItem.Enabled =
                    copyToolStripButton.Enabled = copyToolStripMenuItem.Enabled = tb.Selection.Start != tb.Selection.End;
                    printToolStripButton.Enabled = true;
                    btCompile.Enabled = true;
                    btCompile.Text = currentEntryPoint == null ? "Compile" : "Run";
                    btCompile.ToolTipText = btCompile.Text + " (F5)";
                }
                else
                {
                    saveToolStripButton.Enabled = saveToolStripMenuItem.Enabled = false;
                    saveAsToolStripMenuItem.Enabled = false;
                    cutToolStripButton.Enabled = cutToolStripMenuItem.Enabled =
                    copyToolStripButton.Enabled = copyToolStripMenuItem.Enabled = false;
                    pasteToolStripButton.Enabled = pasteToolStripMenuItem.Enabled = false;
                    printToolStripButton.Enabled = false;
                    undoStripButton.Enabled = undoToolStripMenuItem.Enabled = false;
                    redoStripButton.Enabled = redoToolStripMenuItem.Enabled = false;
                    btCompile.Enabled = false;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        private void printToolStripButton_Click(object sender, EventArgs e)
        {
            if(CurrentTB!=null)
            {
                CurrentTB.Print(new PrintDialogSettings());
            }
        }

        bool tbFindChanged = false;

        private void tbFind_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r' && CurrentTB != null)
            {
                Range r = tbFindChanged?CurrentTB.Range.Clone():CurrentTB.Selection.Clone();
                tbFindChanged = false;
                r.End = new Place(CurrentTB[CurrentTB.LinesCount - 1].Count, CurrentTB.LinesCount - 1);
                var pattern = Regex.Replace(tbFind.Text, FastColoredTextBoxNS.FindForm.RegexSpecSymbolsPattern, "\\$0");
                foreach (var found in r.GetRanges(pattern))
                {
                    found.Inverse();
                    CurrentTB.Selection = found;
                    CurrentTB.DoSelectionVisible();
                    return;
                }
                MessageBox.Show("Not found.");
            }
            else
                tbFindChanged = true;
        }

        private void findToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.ShowFindDialog();
        }

        private void replaceToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.ShowReplaceDialog();
        }

        private void PowerfulCSharpEditor_FormClosing(object sender, FormClosingEventArgs e)
        {
            List<FATabStripItem> list = new List<FATabStripItem>();
            foreach (FATabStripItem tab in  tsFiles.Items)
                list.Add(tab);
            foreach (var tab in list)
            {
                TabStripItemClosingEventArgs args = new TabStripItemClosingEventArgs(tab);
                tsFiles_TabStripItemClosing(args);
                if (args.Cancel)
                {
                    e.Cancel = true;
                    return;
                }
                tsFiles.RemoveTab(tab);
            }
        }

        private void tsFiles_TabStripItemSelectionChanged(TabStripItemChangedEventArgs e)
        {
            lvErrors.Items.Clear();
            if (CurrentTB != null)
            {
                CurrentTB.Focus();
            }
        }

        private void backStripButton_Click(object sender, EventArgs e)
        {
            NavigateBackward();
        }

        private void forwardStripButton_Click(object sender, EventArgs e)
        {
            NavigateForward();
        }

        DateTime lastNavigatedDateTime = DateTime.Now;

        private bool NavigateBackward()
        {
            DateTime max = new DateTime();
            int iLine = -1;
            FastColoredTextBox tb = null;
            for (int iTab = 0; iTab < tsFiles.Items.Count; iTab++)
            {
                var t = (tsFiles.Items[iTab].Controls[0] as FastColoredTextBox);
                for (int i = 0; i < t.LinesCount; i++)
                    if (t[i].LastVisit < lastNavigatedDateTime && t[i].LastVisit > max)
                    {
                        max = t[i].LastVisit;
                        iLine = i;
                        tb = t;
                    }
            }
            if (iLine >= 0)
            {
                tsFiles.SelectedItem = (tb.Parent as FATabStripItem);
                tb.Navigate(iLine);
                lastNavigatedDateTime = tb[iLine].LastVisit;
                tb.Focus();
                return true;
            }
            else
                return false;
        }

        private bool NavigateForward()
        {
            DateTime min = DateTime.Now;
            int iLine = -1;
            FastColoredTextBox tb = null;
            for (int iTab = 0; iTab < tsFiles.Items.Count; iTab++)
            {
                var t = (tsFiles.Items[iTab].Controls[0] as FastColoredTextBox);
                for (int i = 0; i < t.LinesCount; i++)
                    if (t[i].LastVisit > lastNavigatedDateTime && t[i].LastVisit < min)
                    {
                        min = t[i].LastVisit;
                        iLine = i;
                        tb = t;
                    }
            }
            if (iLine >= 0)
            {
                tsFiles.SelectedItem = (tb.Parent as FATabStripItem);
                tb.Navigate(iLine);
                lastNavigatedDateTime = tb[iLine].LastVisit;
                tb.Focus();
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// This item appears when any part of snippet text is typed
        /// </summary>
        class DeclarationSnippet : SnippetAutocompleteItem
        {
            public DeclarationSnippet(string snippet)
                : base(snippet)
            {
            }

            public override CompareResult Compare(string fragmentText)
            {
                var pattern = Regex.Replace(fragmentText, FastColoredTextBoxNS.FindForm.RegexSpecSymbolsPattern, "\\$0");
                if (Regex.IsMatch(Text, "\\b" + pattern, RegexOptions.IgnoreCase))
                    return CompareResult.Visible;
                return CompareResult.Hidden;
            }
        }

        /// <summary>
        /// Divides numbers and words: "123AND456" -> "123 AND 456"
        /// Or "i=2" -> "i = 2"
        /// </summary>
        class InsertSpaceSnippet : AutocompleteItem
        {
            string pattern;

            public InsertSpaceSnippet(string pattern)
                : base("")
            {
                this.pattern = pattern;
            }

            public InsertSpaceSnippet()
                : this(@"^(\d*)([a-zA-Z_]+)(\d*)$")
            {
            }

            public override CompareResult Compare(string fragmentText)
            {
                if (Regex.IsMatch(fragmentText, pattern))
                {
                    Text = InsertSpaces(fragmentText);
                    if (Text != fragmentText)
                        return CompareResult.Visible;
                }
                return CompareResult.Hidden;
            }

            public string InsertSpaces(string fragment)
            {
                var m = Regex.Match(fragment, pattern);
                if (m == null)
                    return fragment;
                if (m.Groups[1].Value == "" && m.Groups[3].Value == "")
                    return fragment;
                return (m.Groups[1].Value + " " + m.Groups[2].Value + " " + m.Groups[3].Value).Trim();
            }

            public override string ToolTipTitle
            {
                get
                {
                    return Text;
                }
            }
        }

        /// <summary>
        /// Inerts line break after '}'
        /// </summary>
        class InsertEnterSnippet : AutocompleteItem
        {
            Place enterPlace = Place.Empty;

            public InsertEnterSnippet()
                : base("[Line break]")
            {
            }

            public override CompareResult Compare(string fragmentText)
            {
                var r = Parent.Fragment.Clone();
                while (r.Start.iChar > 0)
                {
                    if (r.CharBeforeStart == '}')
                    {
                        enterPlace = r.Start;
                        return CompareResult.Visible;
                    }

                    r.GoLeftThroughFolded();
                }

                return CompareResult.Hidden;
            }

            public override string GetTextForReplace()
            {
                //extend range
                Range r = Parent.Fragment;
                Place end = r.End;
                r.Start = enterPlace;
                r.End = r.End;
                //insert line break
                return Environment.NewLine + r.Text;
            }

            public override void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e)
            {
                base.OnSelected(popupMenu, e);
                if (Parent.Fragment.tb.AutoIndent)
                    Parent.Fragment.tb.DoAutoIndent();
            }

            public override string ToolTipTitle
            {
                get
                {
                    return "Insert line break after '}'";
                }
            }
        }

        private void autoIndentSelectedTextToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.DoAutoIndent();
        }

        private void commentSelectedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.InsertLinePrefix("//");
        }

        private void uncommentSelectedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CurrentTB.RemoveLinePrefix("//");
        }

        private void cloneLinesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //expand selection
            CurrentTB.Selection.Expand();
            //get text of selected lines
            string text = Environment.NewLine + CurrentTB.Selection.Text;
            //move caret to end of selected lines
            CurrentTB.Selection.Start = CurrentTB.Selection.End;
            //insert text
            CurrentTB.InsertText(text);
        }

        private void cloneLinesAndCommentToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //start autoUndo block
            CurrentTB.BeginAutoUndo();
            //expand selection
            CurrentTB.Selection.Expand();
            //get text of selected lines
            string text = Environment.NewLine + CurrentTB.Selection.Text;
            //comment lines
            CurrentTB.InsertLinePrefix("//");
            //move caret to end of selected lines
            CurrentTB.Selection.Start = CurrentTB.Selection.End;
            //insert text
            CurrentTB.InsertText(text);
            //end of autoUndo block
            CurrentTB.EndAutoUndo();
        }

        private void bookmarkPlusButton_Click(object sender, EventArgs e)
        {
            if(CurrentTB == null) 
                return;
            TbInfo info = CurrentTB.Tag as TbInfo;
            //get UniqueId of current line
            int id = CurrentTB[CurrentTB.Selection.Start.iLine].UniqueId;
            if (info.bookmarksLineId.Contains(id))
                return;
            //add bookmark
            info.bookmarks.Add(id);
            info.bookmarksLineId.Add(id);
            //repaint
            CurrentTB.Invalidate();
        }

        private void bookmarkMinusButton_Click(object sender, EventArgs e)
        {
            if (CurrentTB == null)
                return;
            TbInfo info = CurrentTB.Tag as TbInfo;
            //get UniqueId of current line
            int id = CurrentTB[CurrentTB.Selection.Start.iLine].UniqueId;
            if (!info.bookmarksLineId.Contains(id))
                return;
            //remove bookmark
            info.bookmarks.Remove(id);
            info.bookmarksLineId.Remove(id);
            //repaint
            CurrentTB.Invalidate();
        }

        private void btGotoFunc_DropDownOpening(object sender, EventArgs e)
        {
            var bt = sender as ToolStripDropDownButton;
            bt.DropDownItems.Clear();
            if(CurrentTB!=null)
                try{
                    var list = (CurrentTB.Tag as TbInfo).explorerList;
                    foreach (var item in list)
                    {
                        var mi = bt.DropDownItems.Add(item.funcName);
                        mi.Tag = item.line;
                        mi.Click += (o, a) => 
                            {
                                CurrentTB.Selection.Start = new Place(0, (int)((o as ToolStripItem).Tag));
                                CurrentTB.DoSelectionVisible();
                            };
                    }
                }catch{;}
        }

        private void CLEditorForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.F5)
                Compile();
        }

        private void Compile()
        {
            if(CurrentTB!=null)
            try
            {
                for (int i = 0; i < CurrentTB.LinesCount;i++)
                    CurrentTB[i].BackgroundBrush = null;

                dgvArgs.EndEdit();
                tbResults.Text = "";
                lvErrors.Items.Clear();
                List<ErrorItem> logs;
                bool res;
                string runResult = "";

                if(currentEntryPoint!=null)
                    res = Compiler.CompileAndRun(CurrentTB.Text, currentEntryPoint, GetArgValues(), GetGlobalWorkSizes(), out logs, out runResult);
                else
                    res = Compiler.Compile(CurrentTB.Text, out logs);

                int errCount = 0;
                foreach (ErrorItem item in logs)
                {
                    ListViewItem li = new ListViewItem(new string[]{item.desc, item.line.ToString(), item.pos.ToString()});
                    li.Tag = item;
                    li.ImageIndex = item.type == ErrorItemType.Error ? 0 : 1;
                    lvErrors.Items.Add(li);
                    if (item.type == ErrorItemType.Error)
                    {
                        errCount++;
                        CurrentTB[item.line - 1].BackgroundBrush = Brushes.Pink;
                    }
                }

                if (res)
                {
                    if (runResult == "")
                        MessageBox.Show("Success.\nThe program has no errors.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    else
                    {
                        tabs.SelectedItem = tabDebug;
                        tbResults.Text = runResult;
                    }
                }
                else
                {
                    tabs.SelectedItem = tabErrors;
                    if (errCount > 0)
                        MessageBox.Show(string.Format("Fail.\r\nFound {0} error(s).", errCount), "Fail", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    else
                        MessageBox.Show(string.Format("Fail.\r\nFound some error."), "Fail", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private int[] GetGlobalWorkSizes()
        {
            var s = tbGlobalWorkSizes.Text;
            var parts = s.Trim(',',';').Split(',', ';');
            int[] result = new int[parts.Length];
            for (int i = 0; i < parts.Length; i++)
            {
                int ind = 1;
                int.TryParse(parts[i], out ind);
                result[i] = ind;
            }

            return result;
        }

        private List<string> GetArgValues()
        {
            List<string> result = new List<string>();

            for (int i = 0; i < dgvArgs.RowCount; i++)
                result.Add(dgvArgs[2, i].Value.ToString());

            return result;
        }

        private void btCompile_Click(object sender, EventArgs e)
        {
            Compile();
        }

        private void lvErrors_DoubleClick(object sender, EventArgs e)
        {
            if (lvErrors.SelectedItems.Count > 0 && CurrentTB!=null)
                try
                {
                    var item = lvErrors.SelectedItems[0].Tag as ErrorItem;
                    CurrentTB.Selection.Start = new Place(item.pos - 1, item.line - 1);
                    CurrentTB.DoSelectionVisible();
                    CurrentTB.Focus();
                }
                catch { ; }
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new AboutBox().ShowDialog();
        }

        private void btEntryPoint_DropDownOpening(object sender, EventArgs e)
        {
            var bt = sender as ToolStripDropDownButton;
            bt.DropDownItems.Clear();
            if (CurrentTB != null)
                try
                {
                    {
                        var mi = bt.DropDownItems.Add("<none>");
                        mi.Tag = null;
                        mi.Click += (o, a) =>
                        {
                            var ei = (o as ToolStripItem).Tag as ExplorerItem;
                            bt.Text = "";
                            OnEntryPointSelected(null);
                        };
                    }

                    var list = (CurrentTB.Tag as TbInfo).explorerList;
                    foreach (var item in list)
                    {
                        var mi = bt.DropDownItems.Add(item.funcName);
                        mi.Tag = item;
                        mi.Click += (o, a) =>
                        {
                            var ei = (o as ToolStripItem).Tag as ExplorerItem;
                            bt.Text = ei.funcName;
                            OnEntryPointSelected(ei);
                        };
                    }
                }
                catch { ;}
        }

        ExplorerItem currentEntryPoint = null;

        private void OnEntryPointSelected(ExplorerItem ei)
        {
            BuildDgvArgs(ei);
            currentEntryPoint = ei;
        }

        private void BuildDgvArgs(ExplorerItem ei)
        {
            dgvArgs.Rows.Clear();
            if (ei != null)
                foreach (var arg in ei.args)
                    dgvArgs.Rows.Add(arg.type, arg.name, Compiler.BuildDefaultValue(arg.type));
        }
    }

    public class InvisibleCharsRenderer : Style
    {
        Pen pen;

        public InvisibleCharsRenderer(Pen pen)
        {
            this.pen = pen;
        }

        public override void Draw(Graphics gr, Point position, Range range)
        {
            var tb = range.tb;
            using(Brush brush = new SolidBrush(pen.Color))
            foreach (var place in range)
            {
                switch (tb[place].c)
                {
                    case ' ':
                        {
                            var point = tb.PlaceToPoint(place);
                            point.Offset(tb.CharWidth / 2, tb.CharHeight / 2);
                            gr.DrawLine(pen, point.X, point.Y, point.X + 1, point.Y);
                            if (tb[place.iLine].Count - 1 == place.iChar)
                               goto default;
                            break;
                        }
                    default:
                        if (tb[place.iLine].Count - 1 == place.iChar)
                        {
                            var point = tb.PlaceToPoint(place);
                            point.Offset(tb.CharWidth, 0);
                            gr.DrawString("¶", tb.Font, brush, point);
                        }
                        break;
                }
            }
        }
    }

    public class TbInfo
    {
        // Key - Line.UniqueId
        public HashSet<int> bookmarksLineId = new HashSet<int>();
        // Index - bookmark number, Value - Line.UniqueId
        public List<int> bookmarks = new List<int>();
        //
        public List<ExplorerItem> explorerList = new List<ExplorerItem>();
        //
        public AutocompleteMenu popupMenu;
    }
}
